﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using ThreeByte.ActiveDeck.Publish.Model;


//Based heavily on http://laharsub.codeplex.com

namespace ThreeByte.ActiveDeck.Publish
{

    public partial class ActivePublisher
    {

        /// <summary>
        /// Inner class helps encapsulate the details of the publication and completion
        /// </summary>
        class PublishMessageAsyncResult : AsyncResult
        {
            ActivePublisher parent;
            protected ActiveMessage Message { get; private set; }

            public PublishMessageAsyncResult(ActivePublisher parent, string channel, XElement body, AsyncCallback callback, object state)
                : base(callback, state) {

                //Do something with Channel and body
                Message = new ActiveMessage() {
                    Channel = channel,
                    Body = body
                };

                this.parent = parent;
                this.StartPublish(true);
            }

            public void StartPublish(bool synchronous) {
                PublishedChannel publishedChannel = this.PublishCore();
                this.StartResumeBookmarks(publishedChannel, synchronous);
            }

            PublishedChannel PublishCore() {
                PublishedChannel publishedChannel = null;
                this.parent.channelsLock.EnterUpgradeableReadLock();
                try {
                    bool channelExisted = true;
                    if(!this.parent.Channels.TryGetValue(this.Message.Channel, out publishedChannel)) {
                        this.parent.channelsLock.EnterWriteLock();
                        try {
                            if(!this.parent.Channels.TryGetValue(this.Message.Channel, out publishedChannel)) {
                                channelExisted = false;
                                publishedChannel = new PublishedChannel();
                                this.AddMessageToChannel(publishedChannel, DateTime.Now, 0);
                                this.parent.Channels.Add(this.Message.Channel, publishedChannel);
                            }
                        } finally {
                            this.parent.channelsLock.ExitWriteLock();
                        }
                    }
                    if(channelExisted) {
                        publishedChannel.Lock.EnterWriteLock();
                        try {
                            DateTime now = DateTime.Now;
                            int totalSizeReduction = this.parent.PruneExpiredMessages(publishedChannel, now);
                            this.AddMessageToChannel(publishedChannel, now, totalSizeReduction);
                        } finally {
                            publishedChannel.Lock.ExitWriteLock();
                        }
                    }
                } finally {
                    this.parent.channelsLock.ExitUpgradeableReadLock();
                }

                return publishedChannel;
            }

            void AddMessageToChannel(PublishedChannel publishedChannel, DateTime now, int totalSizeReduction) {
                lock(this.parent.syncRoot) {
                    this.Message.MessageId = ++this.parent.maxMessageId;
                    this.parent.currentNumberMessages++;// += this.Message.Body.Length - totalSizeReduction;
                }
                publishedChannel.Messages.Add(new PublishedMessage {
                    Expire = now.Add(this.parent.ttl),
                    ActiveMessage = this.Message
                });
            }

            void StartResumeBookmarks(PublishedChannel publishedChannel, bool synchronous) {
                Bookmark[] bookmarksToResume = null;
                publishedChannel.Lock.EnterReadLock();
                try {
                    bookmarksToResume = (from bookmark in publishedChannel.Bookmarks
                                         where bookmark.From <= this.Message.MessageId
                                         select bookmark).ToArray();
                } finally {
                    publishedChannel.Lock.ExitReadLock();
                }

                Exception callbackException = null;
                try {
                    foreach(Bookmark bookmark in bookmarksToResume) {
                        // TODO consider scheduling bookmark resumption on a worker thread
                        bookmark.Continuation.ResumeOnBookmark(this.Message);
                    }
                } catch(Exception e) {
                    callbackException = e;
                }
                this.Complete(synchronous, callbackException);
            }

            public static int End(IAsyncResult result) {
                PublishMessageAsyncResult r = AsyncResult.End<PublishMessageAsyncResult>(result);
                return r.Message.MessageId;
            }

        }



    }
}